/**
 * Copyright (C) 2013 Christos Koufogiannakis
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.easychart.chart;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint.Style;
import android.graphics.Paint.Align;
import android.util.AttributeSet;

public class BoxPlot extends ChartBaseClass {
	private float boxWidth;
	private float totalBoxWidth;
	private float boxPaddingOnEachSide;	
		
	private List<Double> medians = new ArrayList<Double>();
	private List<Double> upperQuartiles = new ArrayList<Double>();
	private List<Double> lowerQuartiles = new ArrayList<Double>();
	private List<Double> upperWhiskers = new ArrayList<Double>();
	private List<Double> lowerWhiskers = new ArrayList<Double>();
	
	/** 
	 * Constructor for BoxPlot
	 * 
	 * @param context
	 * @param attrs
	 */
	public BoxPlot(Context context, AttributeSet attrs) {			
		super(context, attrs);	
		initialize();
	}
	
	/**
	 * Constructor for BarChart
	 * @param context
	 */
	public BoxPlot(Context context) {
		super(context);
		initialize();	
	}
	
	/**
	 * Initializes boxplot specific parameters
	 */
	private void initialize(){
		parameters.SHOW_VERTICAL_GRID.setValue(false);
	}
	
	/**
	 * Add one more population to the chart. For each population the chart
	 * will show the lower and upper whisker, the median and the upper and lower
	 * quartiles. 
	 * 
	 * @param values is the list of values of the population
	 * @param label is the label of the population. If null value is passed
	 * then the label is assumed to be empty string.
	 * 
	 * @throws IllegalArgumentException if values is null
	 */
	public void addPopulation(List<Double> values, String label)
			throws IllegalArgumentException {
		checkNullity(values);
		
		if (label == null) {
			xLabels.add("");
		}
		else {
			xLabels.add(label);
		}
		
		// calculate some statistics of the population
		// create copy of the values, because we will sort it and
		// we don't want to modify passed list
		List<Double> valuesCopy = new ArrayList<Double>(values);
		Collections.sort(valuesCopy);	
		minY = Math.min(minY, valuesCopy.get(0));
		maxY = Math.max(maxY, valuesCopy.get(valuesCopy.size()-1));		
		lowerWhiskers.add(valuesCopy.get(0));
		upperWhiskers.add(valuesCopy.get(valuesCopy.size()-1));
		medians.add(quartile(valuesCopy,50));
		lowerQuartiles.add(quartile(valuesCopy,25));
		upperQuartiles.add(quartile(valuesCopy,75));
	}   
	
	/**
	 * Given a sorted list of values and a percentage p, it returns 
	 * a value such that p% of the values in the list are less than 
	 * the returned value.
	 * 
	 * @param values
	 * @param percent
	 * @return
	 */
    private static double quartile(List<Double> values, double percent) {
    	int n1 = (int) Math.floor((double) (values.size()+1) * percent / 100) -1;
        int n2 = (int) Math.ceil((double) (values.size()+1) * percent / 100) -1;
        return (values.get(n1)+values.get(n2))/2;
    }
	
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
			
		// do these calculations before calling drawXLabels and drawBars
		totalBoxWidth = plotWidth/xLabels.size();
		boxPaddingOnEachSide = parameters.BOX_PAD_EACH_SIDE_PERCENT_OF_BARWIDTH.getValue() 
				* totalBoxWidth/100;
		boxWidth = totalBoxWidth - 2 * boxPaddingOnEachSide;
		
		super.drawXLabels(canvas, totalBoxWidth, (int) (totalBoxWidth/2));
		
		drawBoxes(canvas);
	}
			
	/**
	 * Draw the boxes
	 * 
	 * @param canvas
	 */
	private void drawBoxes(Canvas canvas){		
		mPaint.setTextAlign(Align.CENTER);
		mPaint.setTextSize(vertexLabelTextSize);
		
		for (int i=0; i < Math.min(medians.size(), xLabels.size()); i++){
			mPaint.setColor(parameters.BOX_COLOR.getValue());
			
			float upperWhiskerHeight = (float) (maxPointHeight * (upperWhiskers.get(i) - minYLabel)/(yLabelRange));
			float lowerWhiskerHeight  = (float) (maxPointHeight * (lowerWhiskers.get(i) - minYLabel)/(yLabelRange));
			float upperQuartileHeight  = (float) (maxPointHeight * (upperQuartiles.get(i) - minYLabel)/(yLabelRange));
			float lowerQuartileHeight  = (float) (maxPointHeight * (lowerQuartiles.get(i) - minYLabel)/(yLabelRange));
			float medianHeight = (float) (maxPointHeight *( medians.get(i) - minYLabel)/(yLabelRange));
								
			float boxXLeft = (i * totalBoxWidth + boxPaddingOnEachSide + plotLeftX);
			float boxXRight = boxXLeft + boxWidth;
			float boxYTop = plotBottomYInside - upperQuartileHeight;
			float boxYBottom = plotBottomYInside - lowerQuartileHeight;
						
			//drawMedian
			float median_y = plotBottomYInside - medianHeight;
			mPaint.setColor(parameters.MEDIAN_COLOR.getValue());
			mPaint.setStrokeWidth(parameters.MEDIAN_LINE_WIDTH.getValue());				
			canvas.drawLine(boxXLeft, median_y, boxXRight, median_y, mPaint);		
			
			// draw box
			mPaint.setColor(parameters.BOX_COLOR.getValue());
			mPaint.setStrokeWidth(parameters.BOX_LINE_WIDTH.getValue());
			mPaint.setStyle(Style.STROKE);
			canvas.drawRect(boxXLeft, boxYTop, boxXRight, boxYBottom, mPaint);	
							
			//draw whiskers
			float upperWhiskerY = plotBottomYInside - upperWhiskerHeight;
			float lowerWhiskerY = plotBottomYInside - lowerWhiskerHeight;
			mPaint.setColor(parameters.WHISKER_COLOR.getValue());
			mPaint.setStrokeWidth(parameters.WHISKER_LINE_WIDTH.getValue());
			canvas.drawLine(boxXLeft, upperWhiskerY, boxXRight, upperWhiskerY, mPaint);
			canvas.drawLine(boxXLeft, lowerWhiskerY, boxXRight, lowerWhiskerY, mPaint);	
			
			float boxMiddle = (boxXLeft + boxXRight)/2;
			canvas.drawLine(boxMiddle, boxYTop, boxMiddle, upperWhiskerY, mPaint);
			canvas.drawLine(boxMiddle, boxYBottom, boxMiddle,lowerWhiskerY, mPaint);
		}
	}
}

